package de.tt.utils;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;


public class ClassLocator {

	private String[] packageNames;
	private ClassLoader classLoader;
	private List<Class<?>> classes = new LinkedList<Class<?>>();

	public ClassLocator(String... packageNames) throws ClassNotFoundException {
		this(Thread.currentThread().getContextClassLoader(), packageNames);
	}

	public ClassLocator(ClassLoader classLoader, String... packageNames) throws ClassNotFoundException {
		this.classLoader = classLoader;
		this.packageNames = packageNames;

		if (classLoader == null)
			throw new ClassNotFoundException("Can't get class loader.");

	}

	/**
	 * get all classes that the filter allows.
	 * 
	 * @param filter
	 * @return
	 */
	// public List<Class> getAllClasses(Filter filter) {
	//
	// }

	/**
	 * get all classes in the given packages.
	 */
	public synchronized List<Class<?>> getAllClasses() throws ClassNotFoundException, IOException {
		classes.clear();

		for (String packageName : packageNames) {

			String path = packageName.replace('.', '/');
			Enumeration<URL> resources = classLoader.getResources(path);
			if (resources == null || !resources.hasMoreElements()) {
				throw new ClassNotFoundException("No resource for " + path);
			}

			while (resources.hasMoreElements()) {
				URL resource = resources.nextElement();
				if (resource.getProtocol().equalsIgnoreCase("FILE")) {
					classes.addAll(loadClassesFromDir(new File(resource.getFile()), packageName));
				}
				else if (resource.getProtocol().equalsIgnoreCase("JAR")) {
					classes.addAll(loadClassesFromJar(resource, packageName));
				}
				else {
					throw new ClassNotFoundException("Unknown protocol on class resource: " + resource.toExternalForm());
				}
			}
		}

		return classes;
	}


	/**
	 * load all classes in the given package and its children packages
	 * 
	 * @param res
	 * @param pckgname
	 * @return
	 * @throws ClassNotFoundException
	 */
	List<Class<?>> loadClassesFromJar(URL res, String pckgname) throws ClassNotFoundException {
		// This will hold a list of directories matching the pckgname.
		// There may be more than one if a package is split over multiple
		// jars/paths
		List<Class<?>> classes = new ArrayList<Class<?>>();
		try {

			JarURLConnection conn = (JarURLConnection) res.openConnection();
			JarFile jar = conn.getJarFile();
			for (JarEntry e : Collections.list(jar.entries())) {

				if (e.getName().startsWith(pckgname.replace('.', '/')) && e.getName().endsWith(".class")
						&& !e.getName().contains("$")) {
					String className = e.getName().replace("/", ".").substring(0, e.getName().length() - 6);
					classes.add(Class.forName(className));
				}
			}
		}
		catch (NullPointerException x) {
			throw new ClassNotFoundException(pckgname + " does not appear to be "
					+ "a valid package (Null pointer exception)");

		}
		catch (IOException ioex) {
			throw new ClassNotFoundException("IOException was thrown when trying " + "to get all resources for "
					+ pckgname);
		}
		return classes;
	}

	/**
	 * Recursive method used to find all classes in a given directory and subdirs.
	 * 
	 * @param directory The base directory
	 * @param packageName The package name for classes found inside the base directory
	 * @return The classes
	 * @throws ClassNotFoundException
	 */

	private synchronized List<Class<?>> loadClassesFromDir(File directory, String packageName)
			throws ClassNotFoundException {
		List<Class<?>> classes = new ArrayList<Class<?>>();
		if (!directory.exists()) {
			return classes;
		}
		File[] files = directory.listFiles();
		for (File file : files) {
			if (file.isDirectory() && !file.getName().contains(".svn")) {
				assert !file.getName().contains(".");
				classes.addAll(loadClassesFromDir(file, packageName + "." + file.getName()));
			}
			else if (file.getName().endsWith(".class") && !file.getName().contains("$")) {
				String name = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
				try {
					classes.add(Class.forName(name));
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return classes;
	}
}
